package common

const (
	OpMode_iABC byte = iota
	OpMode_iABx
	OpMode_iAsBx
)

const (
	SIZE_OP = 6
	SIZE_A  = 8
	SIZE_B  = 9
	SIZE_C  = 9
	SIZE_Bx = SIZE_C + SIZE_B
)

const (
	POS_OP = 0
	POS_A  = POS_OP + SIZE_OP
	POS_C  = POS_A + SIZE_A
	POS_B  = POS_C + SIZE_C
	POS_Bx = POS_C
)

/*
iABC B:9|C:9|A:8|opCode:6
iABx Bx:18|A:8|opCode:6
iAsBx sBx:18|A:8|opCode:6
*/

const (
	/*----------------------------------------------------------------------
	name		args	description
	------------------------------------------------------------------------*/
	OP_MOVE     byte = iota /*	A B	R(A) := R(B)					*/
	OP_LOADK                /*	A Bx	R(A) := Kst(Bx)					*/
	OP_LOADBOOL             /*	A B C	R(A) := (Bool)B; if (C) pc++			*/
	OP_LOADNIL              /*	A B	R(A) := ... := R(B) := nil			*/
	OP_GETUPVAL             /*	A B	R(A) := UpValue[B]				*/

	OP_GETGLOBAL /*	A Bx	R(A) := Gbl[Kst(Bx)]				*/
	OP_GETTABLE  /*	A B C	R(A) := R(B)[RK(C)]				*/

	OP_SETGLOBAL /*	A Bx	Gbl[Kst(Bx)] := R(A)				*/
	OP_SETUPVAL  /*	A B	UpValue[B] := R(A)				*/
	OP_SETTABLE  /*	A B C	R(A)[RK(B)] := RK(C)				*/

	OP_NEWTABLE /*	A B C	R(A) := {} (size = B,C)				*/

	OP_SELF /*	A B C	R(A+1) := R(B); R(A) := R(B)[RK(C)]		*/

	OP_ADD /*	A B C	R(A) := RK(B) + RK(C)				*/
	OP_SUB /*	A B C	R(A) := RK(B) - RK(C)				*/
	OP_MUL /*	A B C	R(A) := RK(B) * RK(C)				*/
	OP_DIV /*	A B C	R(A) := RK(B) / RK(C)				*/
	OP_MOD /*	A B C	R(A) := RK(B) % RK(C)				*/
	OP_POW /*	A B C	R(A) := RK(B) ^ RK(C)				*/
	OP_UNM /*	A B	R(A) := -R(B)					*/
	OP_NOT /*	A B	R(A) := not R(B)				*/
	OP_LEN /*	A B	R(A) := length of R(B)				*/

	OP_CONCAT /*	A B C	R(A) := R(B).. ... ..R(C)			*/

	OP_JMP /*	sBx	pc+=sBx					*/

	OP_EQ /*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
	OP_LT /*	A B C	if ((RK(B) <  RK(C)) ~= A) then pc++            */
	OP_LE /*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++            */

	OP_TEST    /*	A C	if not (R(A) <=> C) then pc++			*/
	OP_TESTSET /*	A B C	if (R(B) <=> C) then R(A) := R(B) else pc++	*/

	OP_CALL     /*	A B C	R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
	OP_TAILCALL /*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
	OP_RETURN   /*	A B	return R(A), ... ,R(A+B-2)	(see note)	*/

	OP_FORLOOP /*	A sBx	R(A)+=R(A+2);
		if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
	OP_FORPREP /*	A sBx	R(A)-=R(A+2); pc+=sBx				*/

	OP_TFORLOOP /*	A C	R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
		if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++	*/
	OP_SETLIST /*	A B C	R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B	*/

	OP_CLOSE   /*	A       close all variables in the stack up to (>=) R(A)*/
	OP_CLOSURE /*	A Bx	R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))	*/

	OP_VARARG /*	A B	R(A), R(A+1), ..., R(A+B-1) = vararg		*/
)

type OpCode struct {
	Code byte   // 8 bits
	A    uint16 // 9 bits
	B    uint16 // 9 bits
	C    uint16 // 9 bits
	Bx   uint32 // 18 bits
	SBx  int32  // signed Bx
}

type OpDesc struct {
	Code   byte
	OpMode byte
	OpName string
}

var OpTable = []OpDesc{
	{Code: OP_MOVE, OpMode: OpMode_iABC, OpName: "OP_MOVE"},
	{Code: OP_LOADK, OpMode: OpMode_iABx, OpName: "OP_LOADK"},
	{Code: OP_LOADBOOL, OpMode: OpMode_iABC, OpName: "OP_LOADBOOL"},
	{Code: OP_LOADNIL, OpMode: OpMode_iABC, OpName: "OP_LOADNIL"},
	{Code: OP_GETUPVAL, OpMode: OpMode_iABC, OpName: "OP_GETUPVAL"},
	{Code: OP_GETGLOBAL, OpMode: OpMode_iABx, OpName: "OP_GETGLOBAL"},
	{Code: OP_GETTABLE, OpMode: OpMode_iABC, OpName: "OP_GETTABLE"},
	{Code: OP_SETGLOBAL, OpMode: OpMode_iABx, OpName: "OP_SETGLOBAL"},
	{Code: OP_SETUPVAL, OpMode: OpMode_iABC, OpName: "OP_SETUPVAL"},
	{Code: OP_SETTABLE, OpMode: OpMode_iABC, OpName: "OP_SETTABLE"},
	{Code: OP_NEWTABLE, OpMode: OpMode_iABC, OpName: "OP_NEWTABLE"},
	{Code: OP_SELF, OpMode: OpMode_iABC, OpName: "OP_SELF"},
	{Code: OP_ADD, OpMode: OpMode_iABC, OpName: "OP_ADD"},
	{Code: OP_SUB, OpMode: OpMode_iABC, OpName: "OP_SUB"},
	{Code: OP_MUL, OpMode: OpMode_iABC, OpName: "OP_MUL"},
	{Code: OP_DIV, OpMode: OpMode_iABC, OpName: "OP_DIV"},
	{Code: OP_MOD, OpMode: OpMode_iABC, OpName: "OP_MOD"},
	{Code: OP_POW, OpMode: OpMode_iABC, OpName: "OP_POW"},
	{Code: OP_UNM, OpMode: OpMode_iABC, OpName: "OP_UNM"},
	{Code: OP_NOT, OpMode: OpMode_iABC, OpName: "OP_NOT"},
	{Code: OP_LEN, OpMode: OpMode_iABC, OpName: "OP_LEN"},
	{Code: OP_CONCAT, OpMode: OpMode_iABC, OpName: "OP_CONCAT"},
	{Code: OP_JMP, OpMode: OpMode_iAsBx, OpName: "OP_JMP"},
	{Code: OP_EQ, OpMode: OpMode_iABC, OpName: "OP_EQ"},
	{Code: OP_LT, OpMode: OpMode_iABC, OpName: "OP_LT"},
	{Code: OP_LE, OpMode: OpMode_iABC, OpName: "OP_LE"},
	{Code: OP_TEST, OpMode: OpMode_iABC, OpName: "OP_TEST"},
	{Code: OP_TESTSET, OpMode: OpMode_iABC, OpName: "OP_TESTSET"},
	{Code: OP_CALL, OpMode: OpMode_iABC, OpName: "OP_CALL"},
	{Code: OP_TAILCALL, OpMode: OpMode_iABC, OpName: "OP_TAILCALL"},
	{Code: OP_RETURN, OpMode: OpMode_iABC, OpName: "OP_RETURN"},
	{Code: OP_FORLOOP, OpMode: OpMode_iAsBx, OpName: "OP_FORLOOP"},
	{Code: OP_FORPREP, OpMode: OpMode_iAsBx, OpName: "OP_FORPREP"},
	{Code: OP_TFORLOOP, OpMode: OpMode_iABC, OpName: "OP_TFORLOOP"},
	{Code: OP_SETLIST, OpMode: OpMode_iABC, OpName: "OP_SETLIST"},
	{Code: OP_CLOSE, OpMode: OpMode_iABC, OpName: "OP_CLOSE"},
	{Code: OP_CLOSURE, OpMode: OpMode_iABx, OpName: "OP_CLOSURE"},
	{Code: OP_VARARG, OpMode: OpMode_iABx, OpName: "OP_VARARG"},
}

func GetOpMode(opCode byte) *OpDesc {
	if int(opCode) < len(OpTable) {
		return &OpTable[opCode]
	} else {
		return nil
	}
}

func OpLoadK(A uint16, Bx uint32) *OpCode {
	opCode := &OpCode{}
	opCode.Code = OP_LOADK
	opCode.A = A
	opCode.Bx = Bx
	return opCode
}

func OpReturn(A uint16, B uint16) *OpCode {
	opCode := &OpCode{}
	opCode.Code = OP_RETURN
	opCode.A = A
	opCode.B = B
	return opCode
}

func (this *OpCode) WriteToBuffer(buffer *Buffer) {
	var data int32
	opDesc := OpTable[this.Code]
	switch opDesc.OpMode {
	case OpMode_iABC:
		data = int32(this.Code)<<POS_OP |
			int32(this.A)<<POS_A |
			int32(this.B)<<POS_B |
			int32(this.C)<<POS_C
	case OpMode_iABx:
		data = int32(this.Code)<<POS_OP |
			int32(this.A)<<POS_A |
			int32(this.Bx)<<POS_Bx
	case OpMode_iAsBx:
		data = int32(this.Code)<<POS_OP |
			int32(this.A)<<POS_A |
			int32(this.SBx)<<POS_Bx
	}
	buffer.WriteInt32(data)
}
